﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConversionHelper.cs" company="DevDigital">
//   DevDigital
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Settings.NET
{
    using System;
    using System.ComponentModel;
    using System.Linq.Expressions;

    /// <summary>
    /// Conversion helper
    /// </summary>
    internal class ConversionHelper
    {
        /// <summary>
        /// Determines whether the type is a predefined value type.
        /// The predefined value types are:
        ///     Numeric
        ///         sbyte, short, int, long
        ///         byte, ushort, uint, ulong
        ///         float, double, decimal
        ///     Logical
        ///         bool
        ///     Character
        ///         char        
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <returns>
        ///   <c>true</c> if the type is a predefined value type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsPredefinedValueType<TValue>()
        {
            var predefinedValueType =
                typeof(TValue).IsPrimitive || typeof(TValue) == typeof(decimal);
            return predefinedValueType;
        }

        /// <summary>
        /// Determines whether the type is a predefined value type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the type is a predefined value type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsPredefinedValueType<TValue>(TValue value)
        {
            return IsPredefinedValueType<TValue>();
        }

        /// <summary>
        /// Determines whether the type is a primitive type.
        /// The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <returns>
        ///   <c>true</c> if the type is a primitive type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsPrimitiveType<TValue>()
        {
            return typeof(TValue).IsPrimitive;
        }

        /// <summary>
        /// Determines whether the type is a primitive type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the type is a primitive type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsPrimitiveType<TValue>(TValue value)
        {
            return IsPrimitiveType<TValue>();
        }

        /// <summary>
        /// Determines whether the type is a value type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if is value type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsValueType<TValue>(TValue value)
        {
            return IsValueType<TValue>();
        }

        /// <summary>
        /// Determines whether the type is a value type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>        
        /// <returns>
        ///   <c>true</c> if is value type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsValueType<TValue>()
        {
            return typeof(TValue).IsValueType;
        }

        /// <summary>
        /// Determines whether the type is a nullable type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <returns>
        ///   <c>true</c> if the type is a nullable type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullableType<TValue>()
        {
            var type = typeof(TValue);
            return Nullable.GetUnderlyingType(type) != null;
        }

        /// <summary>
        /// Determines whether the type is a nullable type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the type is a nullable type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullableType<TValue>(TValue value)
        {
            return IsNullableType<TValue>();
        }

        /// <summary>
        /// Determines whether the type can be assigned a null value.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <returns>
        ///   <c>true</c> if the type can be assigned a null value; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullAssignableType<TValue>()
        {            
            var type = typeof(TValue);

            if (!type.IsValueType)
            {
                return true;
            }

            return Nullable.GetUnderlyingType(type) != null;
        }

        /// <summary>
        /// Determines whether the type can be assigned a null value.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the type can be assigned a null value; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullAssignableType<TValue>(TValue value)
        {
            return (object)value == null || IsNullAssignableType<TValue>();
        }

        /// <summary>
        /// Determines whether the type is a reference type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <returns>
        ///   <c>true</c> if the type is a reference type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsReferenceType<TValue>()
        {
            return !IsValueType<TValue>();
        }

        /// <summary>
        /// Determines whether the type is a reference type.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        /// <c>true</c> if is a reference type; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsReferenceType<TValue>(TValue value)
        {
            return IsReferenceType<TValue>();
        }

        /// <summary>
        /// Determines whether the specified value is a string.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>        
        /// <returns>
        /// <c>true</c> if the specified value is a string; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsString<TValue>()
        {
            return typeof(TValue) == typeof(string);
        }

        /// <summary>
        /// Determines whether the specified value is a string.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        /// <c>true</c> if the specified value is a string; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsString<TValue>(TValue value)
        {
            return IsString<TValue>();
        }

        /// <summary>
        /// Determines whether the specified value is null.
        /// </summary>
        /// <typeparam name="T">The value type</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        /// <c>true</c> if the specified value is null; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNull<T>(T value)
        {
            if (value is ValueType)
            {
                return false;
            }

            return null == (object)value;
        }

        /// <summary>
        /// Determines whether a value is null or empty.
        /// </summary>
        /// <typeparam name="T">The value type</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        /// <c>true</c> if the value is null or empty; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmpty<T>(T value)
        {
            if (IsNull(value))
            {
                return true;
            }

            return IsString(value) ? string.IsNullOrEmpty(value as string) : value.Equals(default(T));
        }

        /// <summary>
        /// Determines whether a value is a struct.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <returns>
        ///   <c>true</c> if this instance is a struct; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsStruct<TValue>()
        {
            return IsValueType<TValue>() && !IsPredefinedValueType<TValue>();
        }

        /// <summary>
        /// Determines whether a value is a struct.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if this instance is a struct; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsStruct<TValue>(TValue value)
        {
            return IsStruct<TValue>();
        }

        /// <summary>
        /// Converts a type to a nullable version.
        /// </summary>
        /// <typeparam name="T">The type of the nullable</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>The nullable version</returns>
        public static T? ToNullable<T>(string value) where T : struct
        {
            var result = new T?();
            
            try
            {
                if (!string.IsNullOrEmpty(value) && value.Trim().Length > 0)
                {
                    var conv = TypeDescriptor.GetConverter(typeof(T));
                    if (conv != null)
                    {
                        var convertFrom = conv.ConvertFrom(value);
                        if (convertFrom != null)
                        {
                            result = (T)convertFrom;
                        }
                    }
                }
            }
            catch
            {                
            }
            
            return result;
        }

        /// <summary>
        /// Returns a string representation of a property name
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="property">The property.</param>
        /// <returns>The property name as a string</returns>
        public static string PropertyName<TProperty>(Expression<Func<TProperty>> property)
        {
            var lambda = (LambdaExpression)property;

            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            }
            else
            {
                memberExpression = (MemberExpression)lambda.Body;
            }

            return memberExpression.Member.Name;
        }
    }
}